package icasue.reflect.handles.method;

import icasue.reflect.adaptor.AdaptorManager;
import icasue.reflect.handles.object.ObjectOF;
import icasue.reflect.handles.util.OFUtil;
import icasue.reflect.exceptions.HandleException;
import icasue.reflect.handles.HandleSupplier;
import icasue.reflect.handles.OFAble;
import icasue.reflect.handles.arrays.AryOF;
import icasue.reflect.handles.classes.ClassOF;
import icasue.reflect.handles.exception.ExceptionOF;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Consumer;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2020/12/2 下午1:18
 * @UpdateDate:
 * @Description:
 */
public class MethodOF extends AdaptorManager implements OFAble {

    /**
     *  setAccessible_  (method)
     *      doc: open method's invoke privileges if method has modified by private.
     *  invoke_method_invoker_paramArray_  (method,invoker,paramValAry)
     *      doc: invoke method by invoker and paramValueAry.
     *  invoke_type_mName_paramArray  (mOfType,mName,paramValAry)
     *      doc: invoke method by mType, mName and paramValueAry, by this process will find method first.
     *  invoke_mType_mName_inst_paramArray (mOfType,mName,instance,paramValAry)
     *      doc: invoke method by mOfType, mName, instance and paramValueAry.
     *  findMethodAndInvoker_type_mName_paramClassArray  (mOfType,mName,paramClassAry)
     *      doc: to find invoker and method by these param which you given, don't execute method's invoke actually.
     *  findMethod_mType_mName_paramClassArray (mOfType,mName,paramClassAry)
     *      doc: to find a target method handle by mOfType, mName, paramArray.
     *
     *  All method check pass.
     */

    public static Consumer<Object> setAccessible_ = (method) -> {
        try {
            if(ClassOF.isAssignableFrom_.test(new Object[]{Method.class,method.getClass()})) {
                Method mtd = Method.class.cast(method);
                if(!ObjectOF.isPublic_.test(mtd.getModifiers()))
                    mtd.setAccessible(true);
            }else
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.setAccessible_ : required param must assignable from java.lang.reflect.Method.");
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("MethodOF.setAccessible_ : occur an error : " , throwable);
        }
    };

    public static HandleSupplier.FunctionAry<Object> invoke_method_invoker_paramArray_ = (params) -> {
        try {
            if(params.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_method_invoker_paramArray_ : required 3 params, [method,invoker,pramValueArray]");
            if(ClassOF.isAssignableFrom_.test(new Object[]{Method.class,params[0].getClass()})
                    && (ObjectOF.isNull_.test(params[2]) || ClassOF.isArray_.test(params[2].getClass()))) {
                MethodOF.setAccessible_.accept(params[0]);
                if (ObjectOF.notNull_.test(params[2]) && ((Object[]) params[2]).length > 0)
                    return Method.class.cast(params[0]).invoke(params[1], (Object[])params[2]);
                else
                    try { return Method.class.cast(params[0]).invoke(params[1]); }catch (Throwable e){
                        return Method.class.cast(params[0]).invoke(params[1], (Object[]) AryOF.create_.apply(new Object[]{Object[].class,0}));
                    }
            }else
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_method_invoker_paramArray_ : first param required assignable from java.lang.reflect.Method," +
                        "second param must be java.lang.reflect.Array<Object>.");
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_method_invoker_paramArray_ : occur an error : " , throwable);
        }
    };

    /**
     * invoke method, by supplier type, methodName, paramValues.
     * this method can find suitable method metadata, and invoker to invoke.
     */
    public static HandleSupplier.FunctionAry<Object> invoke_type_mName_paramArray = (params) -> {
        try {
            if(params.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_type_mName_paramArray : required 3 params, [mRootType,mName,pramValueArray]");
            if(ClassOF.isAssignableFrom_.test(new Object[]{Class.class,params[0].getClass()})
                    && ObjectOF.equals_.test(new Object[]{String.class,params[1].getClass()})
                    && (ObjectOF.isNull_.test(params[2]) || ClassOF.isArray_.test(params[2].getClass()))){
                // construct params for find invoker and method.
                Object[] copy = Arrays.copyOf(params, params.length);
                copy[2] = ClassOF.changeParamArray21_.apply(copy);
                //find invoker and method.
                Object[] methodAndInvoker = MethodOF.findMethodAndInvoker_type_mName_paramClassArray.apply(copy);
                //do invoke.
                Object[] prepareInvoke = Arrays.copyOf(methodAndInvoker, 3);
                prepareInvoke[2] = params[2];
                return MethodOF.invoke_method_invoker_paramArray_.apply(prepareInvoke);
            } else
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_type_mName_paramArray : first param required java.lang.Class," +
                        "second param must be java.lang.String, third param must be java.lang.reflect.Array<Object>.");
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            if(throwable instanceof InvocationTargetException){

            }
            throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_type_mName_paramArray : occur an error : " , throwable);
        }
    };


    /**
     * Invoke by mOfType, mName, instance and paramValueAry.
     */
    public static HandleSupplier.FunctionAry<Object> invoke_mType_mName_inst_paramArray = (params) -> {
        try {
            if(params.length != 4)
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_mType_mName_inst_paramArray : required 4 params, [mRootType,mName,instance,pramValueArray]");
            if(ClassOF.isAssignableFrom_.test(new Object[]{Class.class,params[0].getClass()})
                    && ObjectOF.equals_.test(new Object[]{String.class,params[1].getClass()})
                    && (ObjectOF.isNull_.test(params[2]) || ClassOF.isAssignableFrom_.test(params[0],params[2].getClass()))
                    && (ObjectOF.isNull_.test(params[3]) || ClassOF.isArray_.test(params[3].getClass()))){
                // construct params for find method.
                Object[] copy = Arrays.copyOf(params, 3);
                copy[2] = params[3];
                copy[2] = ClassOF.changeParamArray21_.apply(copy);
                //find method, then do invoke.
                Object targetMethod = MethodOF.findMethod_mType_mName_paramClassArray.apply(copy);
                return MethodOF.invoke_method_invoker_paramArray_.apply(targetMethod,params[2],params[3]);
            } else
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_mType_mName_inst_paramArray : first param required java.lang.Class," +
                        "second param must be java.lang.String, third param must instance of mOfType, fourth param must be java.lang.reflect.Array<Object>.");
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("MethodOF.invoke_mType_mName_inst_paramArray : occur an error : " , throwable);
        }
    };

    /**
     * find invoker in applicationContext or beanFactory, else construct new instance.
     */
    public static HandleSupplier.FunctionAry<Object[]> findMethodAndInvoker_type_mName_paramClassArray = (params) -> {
        try {
            if(params.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findInvokerAndMethod_type_mName_paramClassArray : required 3 params, [mRootType,mName,pramClassArray]");
            if(ClassOF.isAssignableFrom_.test(new Object[]{Class.class,params[0].getClass()})
                    && ObjectOF.equals_.test(new Object[]{String.class,params[1].getClass()})
                    && (ObjectOF.isNull_.test(params[2]) || ClassOF.isArray_.test(params[2].getClass()))) {
                // find method.
                Object[] copy = Arrays.copyOf(params, params.length);
                Method targetMethod = (Method) ObjectOF.getNull_.get();
                for ( ; ObjectOF.isNull_.test(targetMethod) && ObjectOF.notNull_.test(copy[0]); copy[0] = ClassOF.getSuperclass_.apply(copy[0]) ){
                    try { targetMethod = ClassOF.getDeclaredMethod_.apply(copy); } catch (Throwable e){
                        targetMethod = (Method) ObjectOF.getNull_.get();
                    }
                }
                if(ObjectOF.notNull_.test(targetMethod)) {
                    MethodOF.setAccessible_.accept(targetMethod);
                    // find invoker.
                    Object[] methodAndInvoker = new Object[]{targetMethod,ObjectOF.getNull_.get()};
                    if(!Modifier.isStatic(targetMethod.getModifiers())){


                        // try check base module load or not, stronger this function.
                        ApplicationTouch baseAppTouch = getInst(ApplicationTouch.class);

                        if(baseAppTouch.modelIfLoad()){

                            //check environment ready.
                            if(baseAppTouch.fieldGet("canInvoke", null, Boolean.class).get()){
                                Object applicationContext = baseAppTouch.fieldGet("applicationContext", null, Object.class).get();
                                Object beanFactory = baseAppTouch.fieldGet("beanFactory", null, Object.class).get();
                                try {
                                    methodAndInvoker[1] = OFUtil.cast(MethodOF.findMethod_mType_mName_paramClassArray.apply(
                                            applicationContext.getClass(),
                                            "getBean",
                                            new Class[]{Class.class}
                                    ),Method.class).invoke(applicationContext,(Class)params[0]);
                                }catch (Throwable e){
                                    try {
                                        methodAndInvoker[1] = OFUtil.cast(MethodOF.findMethod_mType_mName_paramClassArray.apply(
                                                beanFactory.getClass(),
                                                "getBean",
                                                new Class[]{Class.class}
                                        ),Method.class).invoke(beanFactory,(Class)params[0]);
                                    }catch (Throwable e2){
                                        methodAndInvoker[1] = ObjectOF.getNull_.get();
                                    }
                                }
                            }
                        }

                        if(ObjectOF.isNull_.test(methodAndInvoker[1])){
                            try { methodAndInvoker[1] = ClassOF.newInstance_.apply(params[0]); }catch (Throwable e3){
                                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findInvokerAndMethod_type_mName_paramClassArray : can't find suitable instance from environment" +
                                        ", and newInstance failed, check if exist noArgs construct pls.");
                            }
                        }
                    }
                    return methodAndInvoker;
                }else{
                    throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findInvokerAndMethod_type_mName_paramClassArray : method not found.");
                }
            } else
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findInvokerAndMethod_type_mName_paramClassArray : first param required java.lang.Class," +
                        "second param must be java.lang.String, third param must be java.lang.reflect.Array<Class>.");
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findInvokerAndMethod_type_mName_paramClassArray : occur an error : " , throwable);
        }
    };


    /**
     * Find a target method handle by mOfType, mName, paramArray.
     */
    public static HandleSupplier.FunctionAry<Object> findMethod_mType_mName_paramClassArray = (params) -> {
        try {
            if(params.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findMethod_mType_mName_paramClassArray : required 3 params, [mRootType,mName,pramClassArray]");
            if(ClassOF.isAssignableFrom_.test(new Object[]{Class.class,params[0].getClass()})
                    && ObjectOF.equals_.test(new Object[]{String.class,params[1].getClass()})
                    && (ObjectOF.isNull_.test(params[2]) || ClassOF.isArray_.test(params[2].getClass()))) {
                // find method.
                Object[] copy = Arrays.copyOf(params, params.length);
                Method targetMethod = (Method) ObjectOF.getNull_.get();

                try {
                    Class java8_MethodUtil = null;
                    try {
                        // using java1.8's MethodUtil to discovery methods by recursion.
                        java8_MethodUtil = Class.forName("sun.reflect.misc.MethodUtil");

                        Method getMethod_java8 = java8_MethodUtil.getDeclaredMethod("getMethod",
                                new Class[]{Class.class, String.class, Class[].class});
                        setAccessible_.accept(getMethod_java8);
                        targetMethod = (Method) getMethod_java8.invoke(null, (Class) copy[0], (String) copy[1], (Class[]) copy[2]);
                    }catch (Throwable e){
                        // by this case, user using jdk11 or higher, discovery method.
                        targetMethod = Tools.discoveryMethod((Class) copy[0], (String) copy[1], (Class[]) copy[2]);
                    }
                }catch (Throwable error){
                    targetMethod = (Method) ObjectOF.getNull_.get();
                }
                /*for ( ; ObjectOF.isNull_.test(targetMethod) && ObjectOF.notNull_.test(copy[0]); copy[0] = ClassOF.getSuperclass_.apply(copy[0]) ){
                    try { targetMethod = ClassOF.getDeclaredMethod_.apply(copy); } catch (Throwable e){
                        targetMethod = (Method) ObjectOF.getNull_.get();
                    }
                }*/
                if(ObjectOF.notNull_.test(targetMethod)) {
                    MethodOF.setAccessible_.accept(targetMethod);
                    return targetMethod;
                }else{
                    throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findMethod_mType_mName_paramClassArray : method not found.");
                }
            } else
                throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findMethod_mType_mName_paramClassArray : first param required java.lang.Class," +
                        "second param must be java.lang.String, third param must be java.lang.reflect.Array<Class>.");
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("MethodOF.findMethod_mType_mName_paramClassArray : occur an error : " , throwable);
        }
    };


}
